﻿using System;
using System.Collections.Generic;

namespace Sinacor.Infra.UI.Controls.SmartClient
{
    public class SinacorReportDictionary<K, L, V> : Dictionary<K, V>
    {
        internal readonly Dictionary<L, K> subDictionary = new Dictionary<L, K>();
        internal readonly Dictionary<K, L> primaryToSubkeyMapping = new Dictionary<K, L>();

        readonly object lockBase = new object();
        readonly object lockSub = new object();

        public new V this[K primaryKey]
        {
            get
            {
                V item;
                if (TryGetValue(primaryKey, out item))
                    return item;

                throw new KeyNotFoundException("primary key not found: " + primaryKey.ToString());
            }
        }

        public void Associate(L subKey, K primaryKey)
        {
            lock (lockBase)
            {
                if (!base.ContainsKey(primaryKey))
                    throw new KeyNotFoundException(string.Format("The base dictionary does not contain the key '{0}'", primaryKey));

                lock (lockSub)
                {
                    if (subDictionary.ContainsKey(subKey))
                    {
                        subDictionary[subKey] = primaryKey;
                        primaryToSubkeyMapping[primaryKey] = subKey;
                    }
                    else
                    {
                        subDictionary.Add(subKey, primaryKey);
                        primaryToSubkeyMapping.Add(primaryKey, subKey);
                    }
                }
            }
        }

        public bool TryGetValue(L subKey, out V val)
        {
            val = default(V);

            lock (lockSub)
            {
                K ep;
                if (subDictionary.TryGetValue(subKey, out ep))
                {
                    if (!TryGetValue(ep, out val))
                    {
                        return false;
                    }
                }
                else
                {
                    return false;
                }
            }

            return true;
        }

        public L GetSubKey(K primaryKey)
        {
            L result;
            primaryToSubkeyMapping.TryGetValue(primaryKey, out result);
            return result;
        }

        public new bool TryGetValue(K primaryKey, out V val)
        {
            lock (lockBase)
            {
                if (!base.TryGetValue(primaryKey, out val))
                {
                    return false;
                }
            }

            return true;
        }

        public bool ContainsKey(L subKey)
        {
            V val;

            return TryGetValue(subKey, out val);
        }

        public new bool ContainsKey(K primaryKey)
        {
            V val;

            return TryGetValue(primaryKey, out val);
        }

        public new void Remove(K primaryKey)
        {
            lock (lockSub)
            {
                subDictionary.Remove(primaryToSubkeyMapping[primaryKey]);
                primaryToSubkeyMapping.Remove(primaryKey);
            }

            lock (lockBase)
                base.Remove(primaryKey);
        }

        public void Remove(L subKey)
        {
            lock (lockSub)
            {
                lock (lockBase)
                    base.Remove(subDictionary[subKey]);

                primaryToSubkeyMapping.Remove(subDictionary[subKey]);
                subDictionary.Remove(subKey);
            }
        }

        public new void Add(K primaryKey, V val)
        {
            lock (lockBase)
                base.Add(primaryKey, val);
        }

        public void Add(K primaryKey, L subKey, V val)
        {
            lock (lockBase)
                base.Add(primaryKey, val);

            Associate(subKey, primaryKey);
        }

        public V[] CloneValues()
        {
            lock (lockBase)
            {
                V[] values = new V[Values.Count];

                Values.CopyTo(values, 0);

                return values;
            }
        }

        public K[] ClonePrimaryKeys()
        {
            lock (lockBase)
            {
                K[] values = new K[Keys.Count];

                Keys.CopyTo(values, 0);

                return values;
            }
        }

        public L[] CloneSubKeys()
        {
            lock (lockBase)
            {
                L[] values = new L[subDictionary.Keys.Count];

                subDictionary.Keys.CopyTo(values, 0);

                return values;
            }
        }
    }
}